package org.fujene.toolbox;

import org.fujene.structure.Constants;
import org.fujene.structure.LookupPointer;

public class ByteConverter {
	// バイト列を整数に変換
	// バイト列をコピーさせなくてすむように big-endian固定
	public static int byteToInt(byte[] b, int from, int length) {
		int temp = 0;
		if (length > 4 || length <= 0)
			length = 4;

		for (int i = from; i < from + length; i++) {
			// Big endian
			temp <<= 8;
			temp += (b[i] & 0xff);
		}

		// 補数操作
		if (temp >= 0 && ((temp & (1 << (length * 8 - 1))) != 0)) {
			temp -= (1 << (length * 8));
		}

		return temp;
	}

	// バイト列をそのままintにできるように
	public static int byteToInt(byte[] b) {
		return byteToInt(b, 0, b.length);
	}

	public static long byteToLong(byte[] b, int from, int length) {
		int temp = 0;
		if (length > 8 || length <= 0)
			length = 8;

		for (int i = from; i < from + length; i++) {
			// Big endian
			temp <<= 8;
			temp += (b[i] & 0xff);
		}

		// 補数操作
		if (temp >= 0 && ((temp & (1 << (length * 8 - 1))) != 0)) {
			temp -= (1 << (length * 8));
		}

		return temp;
	}

	// バイト列->long
	public static long byteToLong(byte[] b) {
		return byteToLong(b, 0, b.length);
	}

	// int -> byte[] big-endian
	public static byte[] intToByte(int i, int length) {
		assert length < 5;
		byte[] newbyte = new byte[length];

		for (int j = length - 1; j >= 0; j--) {
			newbyte[j] = (byte) (i & 0xFF);
			i >>= 8;
		}

		return newbyte;
	}

	// long -> byte[] big-endian
	public static byte[] longToByte(long l, int length) {
		assert length < 9;
		byte[] newbyte = new byte[length];

		for (int j = length - 1; j >= 0; j--) {
			newbyte[j] = (byte) (l & 0xFF);
			l >>= 8;
		}

		return newbyte;
	}

	// バイト列 -> int列
	public static int[] byteToIntArray(byte[] b, int from, int intlength) {
		assert b.length > intlength * 4;
		int[] result = new int[intlength];
		int pointer = from;

		for (int i = 0; i < result.length; i++, pointer += 4) {
			result[i] = byteToInt(b, pointer, 4);
		}

		return result;
	}

	// バイト列そのままポインタに
	public static LookupPointer getPtr(byte[] b) {
		return getPtr(b, 0);
	}

	public static LookupPointer getRelativePtr(byte[] b) {
		return getRelativePtr(b, 0);
	}

	// バイト列をコピーさせなくてすむように
	public static LookupPointer getPtr(byte[] b, int from) {
		assert b.length >= Constants.BYTE_GLOBALPTR;
		LookupPointer ptr = new LookupPointer(-1, -1);

		ptr.block = byteToInt(b, from, Constants.BYTE_BLOCKPTR);
		ptr.point = byteToInt(b, from + Constants.BYTE_BLOCKPTR,
				Constants.BYTE_LOCALPTR);

		return ptr;
	}

	// 相対ポインタ
	public static LookupPointer getRelativePtr(byte[] b, int from) {
		assert b.length >= Constants.BYTE_RELATIVEPTR;
		LookupPointer ptr = new LookupPointer(-128, -1);

		ptr.block = byteToInt(b, from, Constants.BYTE_RELATIVEBLOCKPTR);
		ptr.point = byteToInt(b, from + Constants.BYTE_RELATIVEBLOCKPTR,
				Constants.BYTE_LOCALPTR);

		return ptr;
	}

	// ポインタ -> バイト列
	public static byte[] setPtr(LookupPointer ptr) {
		assert ptr != null;

		int blockNum = ptr.block;
		int point = ptr.point;
		byte temp = 0;
		byte[] br = new byte[Constants.BYTE_GLOBALPTR];

		for (int i = Constants.BYTE_BLOCKPTR - 1; i >= 0; i--) {
			temp = (byte) (blockNum & 0xFF);
			br[i] = temp;
			blockNum >>= 8;
		}

		for (int i = Constants.BYTE_GLOBALPTR - 1; i >= Constants.BYTE_BLOCKPTR; i--) {
			temp = (byte) (point & 0xFF);
			br[i] = temp;
			point >>= 8;
		}

		return br;
	}

	// 相対ポインタ
	public static byte[] setRelativePtr(LookupPointer ptr) {
		assert ptr != null;

		int blockNum = ptr.block;
		int point = ptr.point;
		byte temp = 0;
		byte[] br = new byte[Constants.BYTE_RELATIVEPTR];

		for (int i = Constants.BYTE_RELATIVEBLOCKPTR - 1; i >= 0; i--) {
			temp = (byte) (blockNum & 0xFF);
			br[i] = temp;
			blockNum >>= 8;
		}

		for (int i = Constants.BYTE_RELATIVEPTR - 1; i >= Constants.BYTE_RELATIVEBLOCKPTR; i--) {
			temp = (byte) (point & 0xFF);
			br[i] = temp;
			point >>= 8;
		}

		return br;
	}

	public static long mergeInteger(int upper, int lower) {
		long val = (upper << 32) + lower;
		return val;
	}

	public static int[] divideLong(long input) {
		int[] vals = new int[] { ((int) (input & 0xFFFFFFFF00000000L) >> 32),
				(int) (input & 0xFFFFFFFF) };
		return vals;
	}
}